package com.beney.server;

import com.beney.server.handler.ServerHandlerInitializer;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.net.InetSocketAddress;

@Component
public class Server {

    private Logger logger = LoggerFactory.getLogger(getClass());

    @Value("${netty.port}")
    private Integer port;

    @Autowired
    private ServerHandlerInitializer serverHandlerInitializer;

    // boss 线程组，用作 acceptor 接收连接
    private EventLoopGroup bossGroup = new NioEventLoopGroup();

    // worker 线程组，用于处理逻辑：读数据 -> 业务逻辑 -> 写数据
    private EventLoopGroup workerGroup = new NioEventLoopGroup();

    // Server Channel
    private Channel channel;

    /**
     * 启动服务端
     * 使用 PostConstruct 注解，保证在 bean 初始化完成之后再执行
     */
    @PostConstruct
    public void start() throws InterruptedException {
        ServerBootstrap b = new ServerBootstrap();
        // 设置启动的各种参数
        b.group(bossGroup, workerGroup)
                .channel(NioServerSocketChannel.class) // 指定服务端 channel 为 nio
                .localAddress(new InetSocketAddress(port)) // 设置服务端地址端口
                .option(ChannelOption.SO_BACKLOG, 1024) // 设置 accept 队列的大小，即最大的连接数
//                .childOption(ChannelOption.SO_KEEPALIVE, true) // 使用 Keepalive，在 TCP 层面心跳保活
                .childOption(ChannelOption.TCP_NODELAY, true) // IM 中消息往往很小，所以允许小数据包发送，降低延迟
                .childHandler(serverHandlerInitializer);
        ChannelFuture future = b.bind().sync();
        // 注册异步回调函数，在完成启动的时候进行调用
        future.addListener((ChannelFutureListener) f -> {
            channel = f.channel();
            logger.info("[start][Server 成功启动在 {} 端口]", port);
        });
    }

    /**
     * 关闭 server
     * 使用注解 PreDestoy 在 bean 即将被 context 删除的时候执行
     */
    @PreDestroy
    public void shutdown() {
        if (channel != null) {
            channel.close();
        }
        bossGroup.shutdownGracefully();
        workerGroup.shutdownGracefully();
    }
}
